home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
AmigActive 23
/
AACD 23.iso
/
AACD
/
Online
/
opennap
/
main.c
< prev
next >
Wrap
C/C++ Source or Header
|
2001-06-08
|
34KB
|
1,285 lines
/* Copyright (C) 2000-1 drscholl@users.sourceforge.net
This is free software distributed under the terms of the
GNU Public License. See the file COPYING for details.
$Id: main.c,v 1.301 2001/03/03 01:00:05 drscholl Exp $ */
/* Modified 29/05/01 : Removed default SHAREDIR, since we can't really assume
for Amiga port : a default directory on the Amiga
30/05/01 : Added include "confdefs.h" since we are not using the
CONFIGURE script
Added define for htons() and ntohs(), which are not
defined in the Amiga GCC 2.7.0 includes
Added value for __auto_socket_vers to permit automatic
opening/closing of bsdsocket.library by socket.o
Added include "extrasocket.h" with inlines for a few
bsdsocket.library functions not emulated by ixemul
Removed call to setsid(), not supported by ixemul
*/
#include "confdefs.h"
#define htons(x) (x)
#define ntohs(x) (x)
unsigned long __auto_socket_vers = 4;
#if defined(WIN32) && !defined(__CYGWIN__)
#include <windows.h>
#include <winsock.h>
#endif /* WIN32 */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include <ctype.h>
#if !defined(WIN32) || defined(__CYGWIN__)
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/time.h>
#endif /* !WIN32 */
#include "opennap.h"
#include "debug.h"
#include "extrasocket.h"
#if DEBUG
#define dprint0(a) printf(a);
#define dprint1(a,b) printf(a,b);
#else
#define dprint0(a)
#define dprint1(a,b)
#endif
/* offset into global.poll[] for the given file descriptor */
#define POFF(fd) global.fdmap[fd]
/*
* Global Variables
*/
global_t global;
LIST *Bans = 0; /* bans on ip addresses / users */
LIST *UserClass = 0;
char Buf[2048]; /* global scratch buffer */
HASH *Channel_Db;
int Client_Queue_Length;
HASH *Channels = 0; /* global channel list */
HASH *Client_Versions;
HASH *Clones;
int Compression_Level = 0;
char *Config_Dir = /*SHAREDIR*/ "";
u_int Connection_Count = 0;
LIST *Destroy = 0;
int Flood_Commands;
int Flood_Time;
LIST *Flooders = 0;
HASH *Hotlist; /* global hotlist */
u_int Interface = INADDR_ANY;
char *Listen_Addr = 0;
int Login_Interval;
int Login_Timeout;
int Max_Browse_Result;
int Max_Client_String;
int Max_Clones;
int Max_Command_Length;
int Max_Connections;
int Max_Hotlist;
int Max_Ignore;
int Max_Reason;
int Max_Search_Results;
int Max_Searches;
int Max_Time_Delta;
int Max_Topic;
int Max_User_Channels; /* default, can be changed in config */
LIST *Mods = 0; /* local mods+ */
int Nick_Expire;
double Num_Gigs = 0; /* in kB */
int Num_Files = 0;
int Ping_Interval;
int Register_Interval;
int Search_Timeout;
char *Server_Alias = 0;
LIST *Server_Auth = 0;
int Server_Chunk = 0;
u_int Server_Ip = 0;
u_int Server_Flags = 0;
char *Server_Name = 0;
LIST *Server_Ports = 0; /* which port(s) to listen on for connections */
int Server_Queue_Length;
int SigCaught = 0;
time_t Server_Start; /* time at which the server was started */
int User_Db_Interval; /* how often to save the user database */
HASH *Users; /* global users list */
int Warn_Time_Delta;
HASH *Who_Was;
int Who_Was_Time = 0;
#ifndef ROUTING_ONLY
int File_Count_Threshold;
HASH *File_Table; /* global file list */
int Local_Files = 0; /* number of files shared by local users */
int Max_Shared;
#if RESUME
HASH *MD5; /* global hash list */
#endif /* RESUME */
int Stats_Port; /* port to listen on for stats info */
#endif /* ! ROUTING_ONLY */
#ifndef WIN32
int Uid;
int Gid;
int Connection_Hard_Limit;
int Max_Data_Size;
int Max_Rss_Size;
#endif
int Max_Nick_Length;
int Max_Channel_Length = 0;
/* local server list. NOTE that this contains pointers into the global.clients
list to speed up server-server message passing */
LIST *Servers = 0;
/* list of all servers in the cluster */
LIST *Server_Links = 0;
/* Cache of server names for caching the user->server pointers */
LIST *Server_Names = 0;
#define BACKLOG 50
void
set_write (int fd)
{
#if HAVE_POLL
global. poll[POFF (fd)].events |= POLLOUT;
#else
FD_SET (fd, &global.write_fds);
#endif
}
void
clear_write (int fd)
{
#if HAVE_POLL
global. poll[POFF (fd)].events &= ~POLLOUT;
#else
FD_CLR (fd, &global.write_fds);
#endif
}
void
set_read (int fd)
{
#if HAVE_POLL
global. poll[POFF (fd)].events |= POLLIN;
#else
FD_SET (fd, &global.read_fds);
#endif
}
void
clear_read (int fd)
{
#if HAVE_POLL
global. poll[POFF (fd)].events &= ~POLLIN;
#else
FD_CLR (fd, &global.read_fds);
#endif
}
#define CLICK 64
void
add_fd (int fd)
{
#if HAVE_POLL
int off;
if (global.poll_max == global.poll_num)
{
global. poll_max += CLICK;
global. poll = REALLOC (global.poll,
sizeof (struct pollfd) * global.poll_max);
for (off = global.poll_num; off < global.poll_max; off++)
{
global. poll[off].fd = -1;
global. poll[off].events = 0;
global. poll[off].revents = 0;
}
}
#endif
/* keep track of the biggest fd we've seen */
if (fd > global.max_fd)
{
#if HAVE_POLL
global. fdmap = REALLOC (global.fdmap,
sizeof (int) * (fd + 1));
for (off = global.max_fd + 1; off < fd + 1; off++)
global. fdmap[off] = -1;
#endif
global. max_fd = fd;
}
#if HAVE_POLL
off = global.fdmap[fd] = global.poll_num++;
global. poll[off].fd = fd;
global. poll[off].events = 0;
global. poll[off].revents = 0;
#endif
}
#if HAVE_POLL
void
remove_fd (int fd)
{
if (fd == -1)
{
ASSERT (0);
return;
}
if (global.fdmap[fd] == -1)
{
ASSERT (0);
return;
}
if (global.fdmap[fd] < global.poll_num - 1)
{
/* swap with the last client */
int i = global.poll[global.poll_num - 1].fd;
ASSERT (i != -1);
ASSERT (global.poll[POFF (fd)].fd == fd);
ASSERT (global.poll[POFF (i)].fd == i);
memcpy (&global.poll[POFF (fd)], &global.poll[POFF (i)],
sizeof (struct pollfd));
global. fdmap[i] = POFF (fd);
}
/* mark as unused */
global. fdmap[fd] = -1;
global. poll_num--;
/* reset the pollfd struct */
global. poll[global.poll_num].fd = -1;
global. poll[global.poll_num].events = 0;
global. poll[global.poll_num].revents = 0;
}
#endif /* HAVE_POLL */
int
add_client (CONNECTION * con, int is_server)
{
/* allocate more space if required */
if (global.clients_max == global.clients_num)
{
global. clients_max += CLICK;
global. clients = REALLOC (global.clients,
sizeof (CONNECTION *) *
global.clients_max);
}
con->id = global.clients_num++;
global. clients[con->id] = con;
add_fd (con->fd);
con->class = CLASS_UNKNOWN;
con->timer = global.current_time; /* set a login timer */
set_nonblocking (con->fd);
set_keepalive (con->fd, 1); /* enable tcp keepalive messages */
if (is_server)
{
/* we are doing a non-blocking connect, wait for the socket to become
* writable
*/
con->connecting = 1;
set_write (con->fd);
}
else
{
/* user connection, wait for some input */
set_read (con->fd);
}
return 0;
}
void
send_all_clients (int tag, const char *fmt, ...)
{
va_list ap;
int len;
int i;
va_start (ap, fmt);
vsnprintf (Buf + 4, sizeof (Buf) - 4, fmt, ap);
va_end (ap);
len = strlen (Buf + 4);
set_tag (Buf, tag);
set_len (Buf, len);
len += 4;
for (i = 0; i < global.clients_num; i++)
if (ISUSER (global.clients[i]))
queue_data (global.clients[i], Buf, len);
}
#ifndef ROUTING_ONLY
static void
report_stats (int fd)
{
int n;
struct sockaddr_in sin;
socklen_t sinsize = sizeof (sin);
float loadavg = 0;
n = accept (fd, (struct sockaddr *) &sin, &sinsize);
if (n == -1)
{
nlogerr ("report_stats", "accept");
return;
}
log ("report_stats: connection from %s:%d", inet_ntoa (sin.sin_addr),
htons (sin.sin_port));
#ifdef linux
{
FILE *f = fopen ("/proc/loadavg", "r");
if (f)
{
fscanf (f, "%f", &loadavg);
fclose (f);
}
else
{
log ("report_stats: /proc/loadavg: %s (errno %d)",
strerror (errno), errno);
}
}
#endif /* linux */
snprintf (Buf, sizeof (Buf), "%d %d %.2f %.0f 0\n", Users->dbsize,
Num_Files, loadavg, Num_Gigs * 1024.);
WRITE (n, Buf, strlen (Buf));
CLOSE (n);
}
#endif /* !ROUTING_ONLY */
static void
update_stats (void)
{
int numServers = list_count (Servers);
time_t delta;
delta = global.current_time - global.last_click;
strcpy (Buf, ctime (&Server_Start));
Buf[strlen (Buf) - 1] = 0;
log ("update_stats: server was started on %s", Buf);
strcpy (Buf, ctime (&global.current_time));
Buf[strlen (Buf) - 1] = 0;
log ("update_stats: current time is %s", Buf);
log ("update_stats: library is %d GB, %d files, %d users",
(int) (Num_Gigs / 1048576.), Num_Files, Users->dbsize);
log ("update_stats: %d local clients, %d linked servers",
global.clients_num - numServers, numServers);
#ifndef ROUTING_ONLY
log ("update_stats: %d local files", Local_Files);
log ("update_stats: File_Table contains %d entries", File_Table->dbsize);
#endif
if (delta > 0)
log ("update_stats: %d searches/sec", global.search_count / delta);
log ("update_stats: User_Db contains %d entries", User_Db->dbsize);
log ("update_stats: %d channels", Channels->dbsize);
if (delta > 0)
log ("update_stats: %d kbytes/sec in, %d kbytes/sec out",
(int) (global.bytes_in / 1024 / delta),
(int) (global.bytes_out / delta / 1024));
global. total_bytes_in += global.bytes_in;
global. total_bytes_out += global.bytes_out;
log ("update_stats: %.0f bytes sent, %.0f bytes received",
global.total_bytes_out, global.total_bytes_in);
/* reset counters */
global. bytes_in = 0;
global. bytes_out = 0;
global. search_count = 0;
global. last_click = global.current_time;
/* since we send the same data to many people, optimize by forming
the message once then writing it out */
send_all_clients (MSG_SERVER_STATS, "%d %d %d", Users->dbsize,
Num_Files, (int) (Num_Gigs / 1048576.));
#ifndef ROUTING_ONLY
/* send live stats to stat server */
stat_server_push ();
#endif
}
/* accept all pending connections */
static void
accept_connection (int s)
{
CONNECTION *cli = 0;
socklen_t sinsize;
struct sockaddr_in sin;
int f;
for (;;)
{
sinsize = sizeof (sin);
/* set an alarm just in case we end up blocking when a client
* disconnects before we get to the accept()
*/
alarm (3);
if ((f = accept (s, (struct sockaddr *) &sin, &sinsize)) < 0)
{
alarm (0);
if (N_ERRNO != EWOULDBLOCK)
nlogerr ("accept_connection", "accept");
return;
}
alarm (0);
if (!acl_connection_allowed (BSWAP32 (sin.sin_addr.s_addr)))
{
log ("accept_connection: connection from %s denied by ACLs",
inet_ntoa (sin.sin_addr));
CLOSE (f);
continue;
}
if ((cli = new_connection ()) == 0)
goto error;
cli->fd = -1;
/* if we have a local connection, use the external
interface so others can download from them */
if (sin.sin_addr.s_addr == inet_addr ("127.0.0.1"))
{
cli->ip = BSWAP32 (Server_Ip);
cli->host = STRDUP (Server_Name);
}
else
{
cli->ip = BSWAP32 (sin.sin_addr.s_addr);
cli->host = STRDUP (inet_ntoa (sin.sin_addr));
}
if (!cli->host)
{
OUTOFMEMORY ("accept_connection");
goto error;
}
cli->port = ntohs (sin.sin_port);
cli->fd = f;
if (add_client (cli, 0 /* not a server */ ))
goto error;
}
/* not reached */
ASSERT (0);
return;
error:
if (cli)
{
if (cli->fd != -1)
CLOSE (cli->fd);
if (cli->host)
FREE (cli->host);
FREE (cli);
}
else
CLOSE (f);
}
static void
usage (void)
{
fprintf (stderr,
"usage: %s [ -bhsv ] [ -c DIR ] [ -p PORT ] [ -l IP ]\n",
PACKAGE);
fprintf (stderr, " -c DIR read config files from DIR (default: %s)\n",
/*SHAREDIR*/"");
fputs (" -b run as a background process (daemon)\n",
stderr);
fputs (" -h print this help message\n", stderr);
fputs
(" -l IP listen only on IP instead of all interfaces\n",
stderr);
fputs (" -p PORT listen on PORT for connections (default: 8888)\n",
stderr);
fputs
(" -s channels may only be created by privileged users\n",
stderr);
fputs (" -v display version information\n", stderr);
exit (0);
}
static void
version (void)
{
fprintf (stderr, "%s %s\n", PACKAGE, VERSION);
fprintf (stderr, "Copyright (C) 2000 drscholl@users.sourceforge.net\n");
exit (0);
}
#ifdef __CYGWIN__
extern char *optarg;
#endif
static int *
args (int argc, char **argv, int *sockfdcount)
{
int i;
LIST *ports = 0, *tmpList;
int iface = -1;
int *sockfd;
int not_root = 1;
int port;
#ifndef WIN32
not_root = (getuid () != 0);
#endif
while ((i = getopt (argc, argv, "bc:hl:p:svD")) != -1)
{
switch (i)
{
case 'b':
Server_Flags |= ON_BACKGROUND;
break;
case 'D':
Server_Flags |= ON_NO_LISTEN; /* dont listen on stats port */
break;
case 'c':
/* ignore the command line option if we're running as root.
* we don't allow non-root users to specify their own config
* files to avoid possible security problems.
*/
if (not_root)
Config_Dir = optarg;
else
{
log ("args: can't use -c when run as root");
exit (1);
}
break;
case 'l':
iface = inet_addr (optarg);
break;
case 'p':
/* don't allow a privileged port to be used from the command line
* if running as root. this can only be specified in the
* configuration file defined at compile time.
*/
port = atoi (optarg);
if (not_root || port > 1024)
{
tmpList = CALLOC (1, sizeof (LIST));
tmpList->data = STRDUP (optarg);
tmpList->next = ports;
ports = tmpList;
}
else
{
log ("args: privileged ports not allowed on command line");
exit (1);
}
break;
case 's':
Server_Flags |= ON_STRICT_CHANNELS;
break;
case 'v':
version ();
break;
default:
usage ();
}
}
#if USE_CHROOT
/* we always use the compiled directory instead of the one on the command
* line here to avoid problems.
*/
if (chroot (/*SHAREDIR*/""))
{
perror ("chroot");
exit (1);
}
if (chdir ("/"))
{
perror ("chdir");
exit (1);
}
/* force the config files to be relative to the chroot jail */
Config_Dir = "/";
/* privs will be dropped later. we still need them to read the the
* config file and set resources.
*/
#endif
#if !defined(WIN32) && !defined(__EMX__)
/* check whether to run in the background */
if (Server_Flags & ON_BACKGROUND)
{
if (fork () == 0)
/*setsid ()*/;
else
exit (0);
}
#endif
if (init_server ())
exit (1);
/* if the interface was specified on the command line, override the
* value from the config file
*/
if (iface != -1)
{
Interface = iface;
Server_Ip = iface;
}
/* if port(s) were specified on the command line, override the values
specified in the config file */
if (!ports)
ports = Server_Ports;
/* create the incoming connections socket(s) */
*sockfdcount = list_count (ports);
/* ensure at least one valid port */
if (*sockfdcount < 1)
{
log ("args: no server ports defined");
exit (1);
}
sockfd = CALLOC (*sockfdcount, sizeof (int));
log ("args: listening on %d sockets", *sockfdcount);
for (i = 0, tmpList = ports; i < *sockfdcount;
i++, tmpList = tmpList->next)
{
if ((sockfd[i] = new_tcp_socket (ON_NONBLOCKING | ON_REUSEADDR)) < 0)
exit (1);
if (bind_interface (sockfd[i], Interface, atoi (tmpList->data)) == -1)
exit (1);
if (listen (sockfd[i], BACKLOG) < 0)
{
nlogerr ("args", "listen");
exit (1);
}
log ("args: listening on %s port %d", my_ntoa (Interface),
atoi (tmpList->data));
if (sockfd[i] > global.max_fd)
global. max_fd = sockfd[i];
}
if (ports != Server_Ports)
list_free (ports, free_pointer);
return sockfd;
}
/* sync in-memory state to disk so we can restore properly */
static void
dump_state (void)
{
userdb_dump (); /* write out the user database */
#ifndef ROUTING_ONLY
save_bans (); /* write out server bans */
dump_channels (); /* write out persistent channels file */
acl_save (); /* save acls */
#endif
}
#ifndef ROUTING_ONLY
static int
init_stats_port (void)
{
int sp = -1;
if (!option (ON_NO_LISTEN) && Stats_Port != -1)
{
/* listen on port 8889 for stats reporting */
if ((sp = new_tcp_socket (ON_REUSEADDR)) == -1)
exit (1);
if (bind_interface (sp, Interface, Stats_Port))
exit (1);
if (listen (sp, BACKLOG))
{
logerr ("main", "listen");
exit (1);
}
if (sp > global.max_fd)
global. max_fd = sp;
}
return sp;
}
#endif
int num_reaped = 0;
/* puts the specified connection on the destroy list to be reaped at the
* end of the main event loop
*/
void
destroy_connection (CONNECTION * con)
{
LIST *list;
ASSERT (validate_connection (con));
/* already destroyed */
if (con->fd == -1)
return;
dprint1 ("destroy_connection: destroying fd %d\n", con->fd);
if (con->destroy)
{
list = list_find (Destroy, con);
if (list)
return; /* already destroyed */
log ("destroy_connection: error, destroyed connection not on Destroy list");
log ("destroy_connection: con->host = %s", con->host);
if (ISUSER (con))
log ("destroy_connection: con->user->nick = %s", con->user->nick);
}
else
num_reaped++;
/* append to the list of connections to destroy */
list = CALLOC (1, sizeof (LIST));
if (!list)
{
OUTOFMEMORY ("destroy_connection");
return;
}
list->data = con;
ASSERT (list_validate (Destroy));
Destroy = list_push (Destroy, list);
ASSERT (Destroy->data == con);
con->destroy = 1;
/* we don't want to read/write anything furthur to this fd */
#if HAVE_POLL
remove_fd (con->fd);
#else
FD_CLR (con->fd, &global.read_fds);
FD_CLR (con->fd, &global.write_fds);
#endif /* HAVE_POLL */
ASSERT (list_count (Destroy) == num_reaped);
}
static void
reap_dead_connection (CONNECTION * con)
{
#if DEBUG
int i;
#endif
ASSERT (validate_connection (con));
#if HAVE_POLL
ASSERT (global.fdmap[con->fd] == -1);
#if DEBUG
/* be certain the fd isn't being polled */
for (i = 0; i < global.poll_num; i++)
ASSERT (global.poll[i].fd != con->fd);
#endif /* DEBUG */
#else
/* this should have already happened, but to it here just to be safe */
FD_CLR (con->fd, &global.read_fds);
FD_CLR (con->fd, &global.write_fds);
#endif
if (con->id < global.clients_num - 1)
{
/* swap this place with the last connection in the array */
global. clients[con->id] = global.clients[global.clients_num - 1];
global. clients[con->id]->id = con->id;
}
global.clients_num--;
global.clients[global.clients_num] = 0;
/* close either the current descriptor */
CLOSE (con->fd);
/* mark that the descriptor has been closed */
con->fd = -1;
/* this call actually free's the memory associated with the connection */
remove_connection (con);
}
/* we can't use list_free(Destroy, reap_dead_connection) here because
* reap_dead_connection might try to access `Destroy', which will be pointed
* to free'd memory. so this function updates `Destroy' in an atomic
* fashion such that if `Destroy' is updated, we have the correct new value.
*/
static void
reap_connections (void)
{
LIST *tmp;
while (Destroy)
{
tmp = Destroy;
Destroy = Destroy->next;
num_reaped--;
reap_dead_connection (tmp->data);
FREE (tmp);
}
ASSERT (num_reaped == 0);
}
static void
flood_expire (void)
{
LIST **list, *tmp;
CONNECTION *con;
for (list = &Flooders; *list;)
{
con = (*list)->data;
if (con->flood_start + Flood_Time < global.current_time)
{
/* flood timer expired, resume reading commands */
set_read (con->fd);
tmp = *list;
*list = (*list)->next;
FREE (tmp);
}
else
list = &(*list)->next;
}
}
/* since server->server data is always queued up so it can be compressed
* in one shot, we have to explicitly call send_queued_data() for each
* server here.
*/
static void
flush_server_data (CONNECTION * con, void *unused)
{
(void) unused;
ASSERT (validate_connection (con));
if (send_queued_data (con) == -1)
destroy_connection (con);
}
#if HAVE_POLL
#define TIMEOUT timeout
#define READABLE(c) (global.poll[global.fdmap[c]].revents & POLLIN)
#define WRITABLE(c) (global.poll[global.fdmap[c]].revents & POLLOUT)
#else
#define TIMEOUT to.tv_sec
#define READABLE(c) FD_ISSET(c,&read_fds)
#define WRITABLE(c) FD_ISSET(c,&write_fds)
#endif
static void
server_input (CONNECTION * con, void *arg)
{
#if HAVE_POLL
(void) arg;
ASSERT (global.fdmap[con->fd] != -1);
ASSERT ((global.poll[POFF (con->fd)].events & POLLIN) !=0);
if (global.poll[POFF (con->fd)].revents & POLLIN)
handle_connection (con);
#else
fd_set *read_fds = (fd_set *) arg;
if (FD_ISSET (con->fd, read_fds))
handle_connection (con);
#endif
}
int
main (int argc, char **argv)
{
int *sockfd; /* server sockets */
int sockfdcount; /* number of server sockets */
int i; /* generic counter */
int numfds;
#ifndef ROUTING_ONLY
int sp;
#endif
#if HAVE_POLL
int timeout;
#else
struct timeval to;
fd_set read_fds, write_fds;
#endif
#if defined(WIN32) && !defined(__CYGWIN__)
WSADATA wsa;
WSAStartup (MAKEWORD (1, 1), &wsa);
#endif /* !WIN32 */
memset (&global, 0, sizeof (global_t));
/* minimize the stack space for the main loop by moving the command line
parsing code to a separate routine */
sockfd = args (argc, argv, &sockfdcount);
#ifndef ROUTING_ONLY
sp = init_stats_port ();
#endif
#if HAVE_POLL
global. poll_max = global.max_fd + 1;
global. poll = CALLOC (global.poll_max, sizeof (struct pollfd));
for (i = 0; i < global.poll_max; i++)
global.poll[i].fd = -1;
global. fdmap =
CALLOC (global.poll_max, sizeof (int) * (global.max_fd + 1));
memset (global.fdmap, -1, sizeof (int) * (global.max_fd + 1));
#endif
for (i = 0; i < sockfdcount; i++)
{
#if HAVE_POLL
struct pollfd *p;
global. fdmap[sockfd[i]] = global.poll_num++;
p = &global.poll[global.fdmap[sockfd[i]]];
p->fd = sockfd[i];
p->events = POLLIN;
#else
FD_SET (sockfd[i], &global.read_fds);
#endif
}
#ifndef ROUTING_ONLY
if (sp != -1)
{
#if HAVE_POLL
global. fdmap[sp] = global.poll_num++;
global. poll[POFF (sp)].fd = sp;
global. poll[POFF (sp)].events = POLLIN;
#else
FD_SET (sp, &global.read_fds);
#endif
}
#endif
/* schedule periodic events */
add_timer (global.stat_click, -1, (timer_cb_t) update_stats, 0);
add_timer (User_Db_Interval, -1, (timer_cb_t) dump_state, 0);
add_timer (60, -1, (timer_cb_t) expire_bans, 0);
add_timer (Ping_Interval, -1, (timer_cb_t) lag_detect, 0);
add_timer (Who_Was_Time, -1, (timer_cb_t) expire_whowas, 0);
/* initialize so we get the correct delta for the first call to
update_stats() */
global. last_click = global.current_time;
/* auto connect remote servers if requested */
if (option (ON_AUTO_LINK))
auto_link ();
/* main event loop */
while (!SigCaught)
{
global. current_time = time (0);
TIMEOUT = next_timer ();
/* if we have a flood list and the timeout is greater than when
* the flood expires, reset the timeout
*/
if (Flooders && TIMEOUT > Flood_Time)
TIMEOUT = Flood_Time;
#if HAVE_POLL
#if DEBUG
/* check to make sure the poll[] array looks kosher */
for (i = 0; i < global.poll_num; i++)
{
ASSERT (global.poll[i].fd != -1);
ASSERT (global.fdmap[global.poll[i].fd] == i);
}
for (i = global.poll_num; i < global.poll_max; i++)
{
ASSERT (global.poll[i].fd == -1);
ASSERT (global.poll[i].events == 0);
ASSERT (global.poll[i].revents == 0);
}
#endif /* DEBUG */
numfds = poll (global.poll, global.poll_num, timeout * 1000);
if (numfds == -1)
{
nlogerr ("main", "poll");
continue;
}
#else
read_fds = global.read_fds;
write_fds = global.write_fds;
to.tv_usec = 0;
numfds = select (global.max_fd + 1, &read_fds, &write_fds, 0, &to);
if (numfds == -1)
{
nlogerr ("main", "select");
continue;
}
#endif
/* pre-read server links */
list_foreach (Servers, (list_callback_t) server_input,
#ifndef HAVE_POLL
&read_fds
#else
NULL
#endif
);
/* do client i/o */
for (i = 0; i < global.clients_num; i++)
{
#if HAVE_POLL
int off = POFF (global.clients[i]->fd);
if (global.poll[off].revents & (POLLNVAL | POLLHUP | POLLERR))
{
if (global.poll[off].revents & POLLERR)
{
int err;
socklen_t errlen = sizeof (err);
/* error */
if (getsockopt (global.poll[off].fd, SOL_SOCKET,
SO_ERROR, &err, &errlen))
logerr ("main", "getsockopt");
else
{
log ("main: fd %d (%s): %s (errno %d)",
global.poll[off].fd,
global.clients[i]->host, strerror (err), err);
}
}
else
log ("main: fd %d %s", global.poll[off].fd,
(global.poll[off].
revents & POLLNVAL) ? "is invalid" : "got hangup");
destroy_connection (global.clients[i]);
continue;
}
#endif
if (READABLE (global.clients[i]->fd))
{
if (!global.clients[i]->destroy)
handle_connection (global.clients[i]);
}
if (WRITABLE (global.clients[i]->fd))
{
if (global.clients[i]->connecting)
complete_connect (global.clients[i]);
else
{
#if HAVE_POLL
/* sanity check - make sure there was actually data to
* write.
*/
if (!ISSERVER (global.clients[i])
&& !global.clients[i]->sendbuf)
{
log ("main: ERROR, fd %d (id %d) was writable with no pending data", global.clients[i]->fd, global.clients[i]->id);
clear_write (global.clients[i]->fd);
}
#endif
if (send_queued_data (global.clients[i]) == -1)
destroy_connection (global.clients[i]);
}
}
/* reap connections which have no logged in after
* `Login_Timeout' seconds
*/
if (ISUNKNOWN (global.clients[i]) &&
global.current_time - global.clients[i]->timer >
Login_Timeout)
{
log ("main: terminating %s (login timeout)",
global.clients[i]->host);
send_cmd (global.clients[i], MSG_SERVER_ERROR,
"Idle timeout");
destroy_connection (global.clients[i]);
}
}
/* handle timed-out remote searches */
expire_searches ();
#ifndef ROUTING_ONLY
/* check for stat server i/o */
if (global.stat_server_fd != -1)
{
if (WRITABLE (global.stat_server_fd))
{
int code;
socklen_t codesize = sizeof (code);
clear_write (global.stat_server_fd);
/* nonblocking connect complete - check connection code */
if (getsockopt (global.stat_server_fd, SOL_SOCKET, SO_ERROR,
&code, &codesize))
{
logerr ("main","getsockopt");
#if HAVE_POLL
remove_fd (global.stat_server_fd);
#endif
}
else if (code)
{
log ("main: connection to stat server failed (%s)",
strerror (code));
#if HAVE_POLL
remove_fd (global.stat_server_fd);
#endif
}
else
set_read (global.stat_server_fd);
}
else if (READABLE (global.stat_server_fd))
stat_server_read ();
}
/* check for stats port connections */
if (sp != -1)
{
#if HAVE_POLL
if (global.poll[POFF (sp)].revents & POLLIN)
#else
if (FD_ISSET (sp, &read_fds))
#endif /* HAVE_POLL */
report_stats (sp);
}
#endif
/* check for new clients */
for (i = 0; i < sockfdcount; i++)
{
#if HAVE_POLL
if (global.poll[POFF (sockfd[i])].revents & POLLIN)
#else
if (FD_ISSET (sockfd[i], &read_fds))
#endif /* HAVE_POLL */
accept_connection (sockfd[i]);
}
list_foreach (Servers, (list_callback_t) flush_server_data, 0);
flood_expire ();
/* execute any pending events now */
exec_timers (global.current_time);
/* remove destroyed connections from the client list. this
* MUST be the last operation in the loop since all the previous
* can cause connections to be terminated.
*/
reap_connections ();
}
/* close all open file descriptors properly */
for (i = 0; i <= global.max_fd; i++)
CLOSE (i);
dump_state ();
#if DEBUG
#if HAVE_POLL
FREE (global.poll);
FREE (global.fdmap);
#endif
for (i = 0; i < global.clients_num; i++)
{
global. clients[i]->fd = -1;
remove_connection (global.clients[i]);
}
FREE (global.clients);
FREE (sockfd);
#ifndef ROUTING_ONLY
free_hash (Filter);
free_hash (File_Table);
#endif
free_hash (Users);
free_hash (Channels);
Channels = 0;
free_hash (Hotlist);
free_hash (User_Db);
free_hash (Who_Was);
free_hash (Clones);
free_hash (Client_Versions);
free_timers ();
list_free (Bans, (list_destroy_t) free_ban);
list_free (Server_Auth, (list_destroy_t) free_server_auth);
list_free (Server_Names, free_pointer);
list_free (Destroy, 0);
/* free up memory associated with global configuration variables */
free_config ();
acl_destroy ();
/* this displays a list of leaked memory. pay attention to this. */
CLEANUP ();
#endif
#if defined(WIN32) && !defined(__CYGWIN__)
WSACleanup ();
#endif
global. current_time = time (0);
log ("main: server ended at %s", ctime (&global.current_time));
exit (0);
}